import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;

/**
 * Class representing a plan
 * 
 */
public class Plan {
        private HashSet<Action> actions;
	private HashSet<OrderingConstraint> orderingConstraints;
	private HashSet<CausalLink> causalLinks;
	private HashSet<Tuple<Action, Predicate>> openPreconditions;
	private HashSet<Predicate> initialState;

	private Action start;
	private Action finish;
	private HashSet<Predicate> goalState;
	
	/**
	 * Constructor for the Plan class
	 * 
	 * @param initialState
	 *            Set of literals in the initial state of the planning problem
	 * @param goalState
	 *            Set of goal literals of the planning problem
	 */
	public Plan(HashSet<Predicate> initialState, HashSet<Predicate> goalState) {
		/*
		 * create initial action set containing the 'start' and 'finish' actions
		 */

		actions = new HashSet<Action>();
		
		this.initialState = initialState;
		this.goalState = goalState;

		// The start below is an action.
		start = new Action("Start", new HashSet<Predicate>(), initialState,
				new HashSet<Predicate>(), new Vector<Resource>(), 0); 
		
		// The finish below is an action, its preconditions defined as
		// goalState which has the type HashSet<Predicate> (Predicate is
		// somewhat a arrayList storing predicates) retrieved from
		// constructor, its addList and deleteList are initialised as empty
		// HashSet
		finish = new Action("Finish", goalState, new HashSet<Predicate>(),
				new HashSet<Predicate>(), new Vector<Resource>(), 0);

		actions.add(start);
		actions.add(finish);

		/*
		 * create the initial set of ordering constraints, i.e. start < finish
		 */

		/*
		 * now we have finish action and therefore we know that the start action
		 * is before finish, so we create this as a ordering constraint and add
		 * it to the constraint list called orderingConstaints
		 */
		orderingConstraints = new HashSet<OrderingConstraint>();

		OrderingConstraint oc = new OrderingConstraint(start, finish);

		orderingConstraints.add(oc);

		/* create the initial (empty) set of causal links */
		causalLinks = new HashSet<CausalLink>();

		/*
		 * create the initial set of open preconditions, which is the
		 * preconditions of the finish state
		 * not in the initialState
		 */
		
		openPreconditions = new HashSet<Tuple<Action, Predicate>>();
		
		for(Predicate p : goalState) {
			if (!SetHelper.contains2(initialState, p)) {
				openPreconditions.add(new Tuple<Action, Predicate>(finish, p));
			}
		}
	}

	/* no-arg constructor */
	public Plan() {
		this(new HashSet<Predicate>(), new HashSet<Predicate>());
	}

	/**
	 * Returns a not yet achieved sub-goal (that is, an action and one of its
	 * preconditions which not yet has been fulfilled)
	 */
	public Tuple<Action, Predicate> selectSubGoal() {
		Iterator<Tuple<Action, Predicate>> iter = openPreconditions.iterator();

		if (iter.hasNext())
			return iter.next();
		else
			return null;
	}

	/**
	 * Returns true if the plan is a solution to the given problem The following
	 * criteria must be met: - causal links establishing all preconditions of
	 * all steps - all threats resolved - all temporal ordering constraints
	 * consistent - all variable bindings consistent
	 */
	public boolean isSolution() {
		return openPreconditions.isEmpty();
	}

	/**
	 * Performs a consistency check on the plan, returning true if the check
	 * succeeds.
	 * 
	 * @return
	 */
	public boolean isConsistent() {
		/*
		 * For each ordering constraint, make sure that there are no loops using
		 * recursive method recIsConsistent
		 */
		for (OrderingConstraint o : orderingConstraints) {
			if (!recIsConsistent(o.getBefore(), o.getAfter())) {
				return false;
			}
		}
		return true;
	}

	private boolean recIsConsistent(Action a, Action b) {
		if (a.equals(b)) {
			// houston, we have a loop
			return false;
		}

		for (OrderingConstraint o : orderingConstraints) {
			if (o.getBefore().equals(b)) {
				// if we have found an ordering constraint, whose 'before'
				// action
				// matches the 'after' action, we continue the tracking
				// recursively
				return recIsConsistent(a, o.getAfter());
			}
		}
		return true;
	}

	/**
	 * Adds a causal link to the plan
	 * 
	 * @param c
	 */
	public void addCausalLink(CausalLink c) {
		causalLinks.add(c);
	}

	/**
	 * Adds an ordering constraint to the plan
	 * 
	 * @param o
	 */
	public void addOrderingConstraint(OrderingConstraint o) {
		orderingConstraints.add(o);
	}

	public void addAction(Action a) {
		actions.add(a);
	}

	/**
	 * Returns the list of the plan's causal links
	 * 
	 * @return
	 */
	public HashSet<CausalLink> getCausalLinks() {
		return causalLinks;
	}

	public HashSet<Tuple<Action, Predicate>> getOpenPreconditions() {
		return openPreconditions;
	}

	public HashSet<OrderingConstraint> getOrderingConstraints() {
		return orderingConstraints;
	}

	/**
	 * Returns the plan's start action, which is a dummy action for ordering
	 * purposes
	 * 
	 * @return
	 */
	public Action start() {
		return start;
	}

	/**
	 * Returns the plan's finish action, which is a dummy action for ordering
	 * purposes
	 * 
	 * @return
	 */
	public Action finish() {
		return finish;
	}

	/**
	 * Returns a Vector containing all the used actions in an order not breaking
	 * any Ordering Constraints
	 * 
	 * @return
	 */
	public Vector<Action> linearize() {

		Vector<Action> as = new Vector<Action>();
		as.add(start);
		int index;

		for (Action a : actions) {
			index = 1;

			if (!a.equals(start)) {
				
				index = getSortingIndex(a, as);
				as.insertElementAt(a, index);
			}
		}
		return as;
	}

	/**
	 * Return an index that a certain action can placed at depending on Ordering
	 * Constraints
	 * 
	 * @return
	 */
	private int getSortingIndex(Action a, Vector<Action> as) {

		int index = 1;
		int acIndex = 0;

		for (Action ac : as) {

			
				if (a.getStartTime() > ac.getStartTime()) {
					acIndex = as.indexOf(ac);
					index = acIndex + 1;
				}
		}
		return index;
	}

	public String toString() {
		String str = "";

		str += "Actions: " + actions + "\n";
		str += "Ordering constraints: " + orderingConstraints + "\n";
		str += "Causal links: " + causalLinks + "\n";
		str += "Open preconditions: " + openPreconditions + "\n";

		return str;
	}

	public HashSet<Action> getActions() {
		return actions;
	}

	public HashSet<Predicate> getInitialState() {
		return initialState;
	}

	public void setInitialState(HashSet<Predicate> initialState) {
		this.initialState = initialState;
	}

	public HashSet<Predicate> getGoalState() {
		return goalState;
	}

	public void setGoalState(HashSet<Predicate> goalState) {
		this.goalState = goalState;
	}
}
